#include <PopupOSAL.hpp>
#include <iostream>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/tcp.h>

using namespace std;
using namespace PopupUtils;

Socket *Socket::newServerSocket(int p_port, int p_nbMaxConnections,
    string *p_errMsg)
{
  sockaddr_in sa;
  memset(&sa, 0, sizeof(sa));

  sa.sin_family = PF_INET;
  sa.sin_port = htons(p_port);

  OSSocketFd _s = socket(AF_INET, SOCK_STREAM, 0);
  if (_s == -1)
  {
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    return 0;
  }

  // Set SO_LINGER option to avoid leaving sockets in TIME_WAIT state
  // for a too long time
  int _val = 1;
  setsockopt(_s, SOL_SOCKET, SO_REUSEADDR, &_val, sizeof(int));

  // Set non-blocking mode
  fcntl(_s, O_NONBLOCK);

  /* bind the socket to the internet address */
  if (bind(_s, (sockaddr *)&sa, sizeof(sockaddr_in)) != 0) {
    ::close(_s);
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    return 0;
  }
  else
  {
    listen(_s, p_nbMaxConnections);
    return new Socket(_s);
  }
}

Socket *Socket::newClientSocket(const std::string  & p_host, int p_port,
    string *p_errMsg, Socket::Mode p_mode)
{
  OSSocketFd _s = socket(AF_INET, SOCK_STREAM, 0);
  if (_s == -1)
  {
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    return 0;
  }

  struct hostent *he = gethostbyname(p_host.c_str());

  if (he == 0) {
    if (p_errMsg) {
      *p_errMsg = "Cannot resolve " + p_host + " server IP address.";
    }
    ::close(_s);
    return 0;
  }

  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(p_port);
  addr.sin_addr = *((in_addr *)he->h_addr);
  memset(&(addr.sin_zero), 0, 8);

  if (::connect(_s, (sockaddr *) &addr, sizeof(sockaddr))) {
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    ::close(_s);
    return 0;
  }

  if (p_mode == NonBlockingSocket) {
    fcntl(_s, O_NONBLOCK);
  }

  return new Socket(_s);
}


Socket::Socket(OSSocketFd s)
  : s(s), refCounter(new int(1)) {
}

Socket::~Socket() {
  if (! --(*refCounter)) {
    close();
    delete refCounter;
  }
}

Socket::Socket(const Socket& o)
 : s(o.s), refCounter(o.refCounter)
{
  (*refCounter)++;
}

Socket& Socket::operator=(Socket& o) {
  (*o.refCounter)++;
  refCounter=o.refCounter;
  s         =o.s;

  return *this;
}

void Socket::close() {
  shutdown(s, 2);
  ::close(s);
}

bool Socket::sendBytes(const unsigned char *p_buff, size_t p_size)
{
  bool  _rc = true;
  size_t _remainingBytes = p_size;
  int _nbBytesSent = 0;

  const unsigned char *_ptr = p_buff;

  while (_rc && _remainingBytes > 0)
  {
    _nbBytesSent = send (s, (const char*) _ptr, _remainingBytes, 0);

    // Error
    if (_nbBytesSent == -1)
    {
      if (errno == EWOULDBLOCK || errno == EAGAIN) {
        usleep(3000); // Sleep 3ms before next attempt
      }
      else {
        error("Socket send() failed");
        _rc = false;
      }
    }
    // Socket closed on the other end
    else if (_nbBytesSent == 0)
    {
      error("Socket closed by foreign host");
      _rc = false;
    }
    // Success
    else
    {
      _remainingBytes -= _nbBytesSent;
      _ptr = _ptr + _nbBytesSent;
    }
  }
  return _rc;
}

bool Socket::receiveBytes(char *p_buff, size_t p_size = 0)
{
  bool  _rc = true;
  size_t _remainingBytes = p_size;
  int _nbBytesRead = 0;

  char *_ptr = p_buff;
	  
  while (_rc && _remainingBytes > 0)
  {
    _nbBytesRead = recv (s, (char*) _ptr, _remainingBytes, 0);

    // Error
    if (_nbBytesRead == -1)
    {
      if (errno == EWOULDBLOCK || errno == EAGAIN) {
        usleep(50000); // Sleep 50ms before next attempt
      }
      else {
        error("Socket recv() failed");
        _rc = false;
      }
    }
    // Socket closed on the other end
    else if (_nbBytesRead == 0)
    {
      error("Socket closed by foreign host");
      _rc = false;
    }
    // Success
    else
    {
      _remainingBytes -= _nbBytesRead;
      _ptr = _ptr + _nbBytesRead;
    }
  }

  return _rc;
}

bool Socket::enableKeepAlive(bool p_doEnable, int p_timeout, KeepAliveHelper*)
{
  int _val = 0;
  bool _rc = true;
  
  if (p_doEnable == true)
  {
    _val = 1;
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &_val, sizeof(int));

#ifdef _LINUX
    _val = p_timeout; // Timeout after which keep alive kicks in
    setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE, &_val, sizeof(int));

    _val = 2; // 2 seconds between each probe
    setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL, &_val, sizeof(int));

    _val = 2; // 2 probes before dropping connection
    setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT, &_val, sizeof(int));

#else
#ifdef _MACOS
    _val = p_timeout; // Timeout after which keep alive kicks in
    setsockopt(s, IPPROTO_TCP, TCP_KEEPALIVE, &_val, sizeof(int));

#else
    _rc = false;
#endif
#endif
  }
  else
  {
    _val = 0;
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &_val, sizeof(int));
  }

  return _rc;
}

Socket* Socket::accept(string *p_errMsg) {
  OSSocketFd new_sock = ::accept(s, 0, 0);
  if (new_sock == -1) {
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    return 0;
  }
  else
  {
	  return new Socket(new_sock);
  }
}
